#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

#define NUM 1024   //字符串最大长度
#define OPT_NUM 64 //最多存在64个选项

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

#define trimSpace(start) do{\
        while(isspace(*start)) ++start;\
    }while(0)

char linecommand[NUM];
char* myargv[OPT_NUM];
int lastcode = 0;//退出码
int lastsig = 0;//退出信号

int redirType = NONE_REDIR;
char* redirFile = NULL;

void commandCheck(char* commands)
{
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);
    
    while(start < end)
    {
        if(*start == '>')
        {
            *start = '\0';
            start++;
            if(*start == '>')
            {
                // "ls -a >> file.log"
                redirType = APPEND_REDIR;
                start++;
            }
            else
            {
                // "ls -a >    file.log"
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            //"cat <      file.txt"
            *start = '\0';
            start++;
            trimSpace(start);
            // 填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}
int main()
{
    while(1)
    {
        redirType = NONE_REDIR;
        redirFile = NULL;
        errno = 0;
        //输入提示符
        printf("用户名@主机名 当前路径# ");
        fflush(stdout);
            
        //获取用户输入
        char* input = fgets(linecommand,sizeof(linecommand)-1,stdin);
        assert(input != NULL);
        (void)input;
        //清除最后一个\n
        linecommand[strlen(linecommand)-1] = 0;
        //printf("test : %s\n",linecommand);
        
        commandCheck(linecommand);
        //切割字符串，即把命令和参数从一个字符串中切割出来依次放进myargv中
        myargv[0] = strtok(linecommand," ");
        int i = 1;
        if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)//给ls指令添加颜色
        {
            myargv[i++] =(char*) "--color=auto";
        }

        while(myargv[i++] = strtok(NULL," "));
        
        //是cd命令就让shell切换到输入的路径，不创建子进程
        if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
        {
            if(myargv[1] != NULL)
                chdir(myargv[1]);
            continue;
        }
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0)
        {
            if(strcmp(myargv[1],"$?") == 0)
            {
                printf("%d,%d\n",lastcode,lastsig);
            }
            else
            {
                printf("%s\n",myargv[1]);
            }
            continue;
        }
        
        //创建子进程
        pid_t id = fork();
        assert(id != -1);

        if(id == 0)//子进程
        {
                // 因为命令是子进程执行的，真正重定向的工作一定要是子进程来完成
            // 如何重定向，是父进程要给子进程提供信息的
            // 这里重定向会影响父进程吗？不会，进程具有独立性
            switch(redirType)
            {
                case NONE_REDIR:
                    // 什么都不做
                    break;
                case INPUT_REDIR:
                    {
                        int fd = open(redirFile, O_RDONLY);
                        if(fd < 0){
                            perror("open");
                            exit(errno);
                        }
                        // 重定向的文件已经成功打开了
                        dup2(fd, 0);
                    }
                    break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        umask(0);
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEND_REDIR) flags |= O_APPEND;
                        else flags |= O_TRUNC;
                        int fd = open(redirFile, flags, 0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd, 1);
                    }
                    break;
                default:
                    printf("bug?\n");
                    break;
            }
            execvp(myargv[0],myargv);
            exit(-1);
        }
        else
        {
            int status= 0;
            pid_t ret = waitpid(id, &status, 0);
            assert(ret > 0);
            (void)ret;
            lastcode = ((status>>8)& 0xFF);
            lastsig = (status & 0x7F);
        }
    }
    return 0;
}
